﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using Wyse.Server.Common;
using System.Threading;
using System.Reflection;

namespace Wyse.Server.Config
{
    /// <summary>
    /// Provides arguments to a config access operation
    /// </summary>
    public class ConfigAccessData
    {
        /// <summary>
        /// Ctor
        /// </summary>
        internal ConfigAccessData(PropertyFile cfg)
        {
            Config = cfg;
        }

        /// <summary>
        /// Gets the config root
        /// </summary>
        public PropertyFile Config
        {
            get;
            private set;
        }

        /// <summary>
        /// Sets a value indicating if the configuration should be saved
        /// </summary>
        public bool ShouldSave
        {
            get;
            set;
        }
    }

    /// <summary>
    /// Delegate used to access the configuration
    /// </summary>
    public delegate void ConfigAccessor(ConfigAccessData data);

    /// <summary>
    /// Manages the server configuration
    /// </summary>
    public sealed class ConfigManager
    {
        #region Static interface

        /// <summary>
        /// Sets the configuration file name and loads it
        /// </summary>
        /// <param name="filename"></param>
        public static void Load(String filename, PropertyFile overr)
        {
            lock (sInstance)
            {
                sInstance.LoadInternal(filename, overr);
            }
        }

        /// <summary>
        /// Provides read access to the configuration.
        /// The accessor should not modify the configuration.
        /// </summary>
        /// <param name="accessor"></param>
        public static void AccessConfig(ConfigAccessor accessor)
        {
            lock (sInstance)
            {
                ConfigAccessData cad = new ConfigAccessData(sInstance.mRoot);
                accessor(cad);

                if (cad.ShouldSave)
                    sInstance.Save();
            }
        }

        /// <summary>
        /// Triggered when the configuration gets reloaded
        /// </summary>
        public static event ConfigAccessor ConfigReloaded;

        #endregion

        #region Implementation

        /// <summary>
        /// Ctor
        /// </summary>
        private ConfigManager()
        {
            LoadDefaults();
        }

        /// <summary>
        /// Sets the configuration file name and loads it
        /// </summary>
        private void LoadInternal(String filename, PropertyFile valuesOverride)
        {
            if (mCfgFilename == filename)
                return;

            if (mWatcher != null)
            {
                mWatcher.Dispose();
                mWatcher = null;
            }

            mOverride = valuesOverride;
            mCfgFilename = filename;

            Reload();

            // start monitoring the file for changes

            mWatcher = new FileSystemWatcher(Directory.GetCurrentDirectory(), mCfgFilename);
            mWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size;
            mWatcher.Changed += new FileSystemEventHandler(mWatcher_Changed);

            WatcherEventsEnabled = true;
        }

        /// <summary>
        /// Saves the configuration
        /// </summary>
        private void Save()
        {
            if (mCfgFilename == null || mRoot == null)
                return;

            try
            {
                WatcherEventsEnabled = false;

                mRoot.Save(mCfgFilename);

                OnReload();
            }
            finally
            {
                WatcherEventsEnabled = true;
            }
        }

        /// <summary>
        /// Reloads the configuration
        /// </summary>
        private void Reload()
        {
            try
            {
                mRoot = new PropertyFile(mCfgFilename);

                OnReload();
            }
            catch (Exception e)
            {
                mRoot = new PropertyFile();

                Logging.Log.Print(Logging.LogLevel.Error, "Failed to load the configuration file ({0}) - using defaults",
                    e.Message);
            }

            if(mOverride != null)
                mRoot.Merge(mOverride);
            mRoot.Defaults = mDefaults;
            mRoot.Seal();
        }

        private bool WatcherEventsEnabled
        {
            get { return mWatcher != null && mWatcher.EnableRaisingEvents; }
            set
            {
                if (mWatcher != null)
                    mWatcher.EnableRaisingEvents = value;
            }
        }

        private void OnReload()
        {
            if (ConfigReloaded != null)
                ConfigReloaded(new ConfigAccessData(mRoot));
        }

        /// <summary>
        /// Loads the default property set
        /// </summary>
        private void LoadDefaults()
        {
            if (mDefaults != null)
                return;

            Assembly asm = typeof(ConfigManager).Assembly;
            Stream stream = asm.GetManifestResourceStream(typeof(ConfigManager), "defaults.properties");

            mDefaults = new PropertyFile(stream);
        }

        static void mWatcher_Changed(object sender, FileSystemEventArgs e)
        {
            ThreadPool.QueueUserWorkItem(new WaitCallback((object o) =>
            {
                lock (sInstance)
                {
                    sInstance.Reload();
                }
            }));
        }

        private static ConfigManager sInstance = new ConfigManager();

        private PropertyFile mOverride;
        private PropertyFile mDefaults;
        private FileSystemWatcher mWatcher;
        private String mCfgFilename;
        private PropertyFile mRoot;

        #endregion
    }
}
